﻿#region header

//  ***************************************************************************
// *                                LICENSE
//  ***************************************************************************
//  * "Haplous" .NET Simple 2D Game Framework
//  * 
//  * Copyright © Jonathan Evans 2014-2014
//  * 
//  * This program is free software; you can redistribute it and/or
//  * modify it under the terms of the GNU General Public License
//  * as published by the Free Software Foundation; either version 2
//  * of the License, or (at your option) any later version.
//  * 
//  * This program is distributed in the hope that it will be useful,
//  * but WITHOUT ANY WARRANTY; without even the implied warranty of
//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  * GNU General Public License for more details.
//  * 
//  * You should have received a copy of the GNU General Public License
//  * along with this program; if not, write to the Free Software
//  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//  * 
//  ***************************************************************************
//  * 
//  * Contact: opencollar@googlemail.com
//  * 
//  * Website: http://haplous.codeplex.com/
//  *
//  * NuGet:   http://www.nuget.org/packages/haplous/
//  *
//  * File:    Haplous/Haplous/PictureSequence.cs
//  *
//  ***************************************************************************

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;

using Haplous.Core;
using Haplous.Correctness;
using Haplous.Properties;

using JetBrains.Annotations;

namespace Haplous
{
    /// <summary>
    ///     A series of pictures that appear one after another.
    /// </summary>
    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    [DebuggerDisplay("PictureSequence: Name={Name}")]
    public abstract class PictureSequence : ElementBase, IEnumerable<Picture>, IPicture
    {
        /// <summary>
        ///     The Cue that will be used to trigger the iteration onto the next picture in the sequence.
        /// </summary>
        [NotNull]
        private readonly Cue _cue;

        /// <summary>
        ///     The name of this picture.
        /// </summary>
        [NotNull]
        private readonly string _name;

        /// <summary>
        ///     A dictionary of the pictures that will be used in this sequence.
        /// </summary>
        [NotNull]
        private readonly RootDictionary<Picture> _pictures = new RootDictionary<Picture>("Pictures");

        /// <summary>
        ///     A token used to control access to the section that updates the picture.
        /// </summary>
        [NotNull]
        private readonly object _tickLock = new object();

        /// <summary>
        ///     The current picture.
        /// </summary>
        [NotNull]
        private Picture _current;

        /// <summary>
        ///     Initializes a new instance of the <see cref="ElementBase" /> class.
        /// </summary>
        /// <param name="game">
        ///     The game to which the class belongs.  The <paramref name="game" /> must not be
        ///     <see langword="null" />.
        /// </param>
        /// <param name="name">
        ///     The unique name that can be used to identify this object.  The <paramref name="name" /> must not be
        ///     <see langword="null" />, empty or all whitespace.
        /// </param>
        /// <param name="cue">
        ///     The Cue that will be used to trigger the iteration onto the next picture in the
        ///     sequence.  The Cue may not be <see langword="null" /> and must come from the
        ///     <see cref="Game.Cues" /> dictionary of the <paramref name="game" /> object supplied.
        /// </param>
        /// <exception cref="Haplous.Core.UnknownResourceException">
        ///     The value provided in the <paramref name="cue" />
        ///     property cannot be found in the supplied <paramref name="game">game's</paramref> <see cref="Game.Cues" />
        ///     dictionary.
        /// </exception>
        protected PictureSequence([NotNull] Game game, [NotNull] string name, [NotNull] Cue cue) : base(game)
        {
            name.ValidateArgument("name", StringMustNotBe.NullOrEmptyOrWhiteSpace);
            cue.ValidateArgument("cue");
            if(!game.Cues.Contains(cue))
                throw new UnknownResourceException("The value provided in the 'Cue' property cannot be found in the supplied game's 'Cues' dictionary.");

            _name = name;
            _cue = cue;
            cue.Tick += Tick;
        }

        /// <summary>
        ///     The Cue that will be used to trigger the iteration onto the next picture in the sequence.
        /// </summary>
        [NotNull]
        protected Cue Cue
        {
            get { return _cue; }
        }

        /// <summary>
        ///     A dictionary of the pictures that will be used in this sequence.
        /// </summary>
        [NotNull]
        public RootDictionary<Picture> Pictures
        {
            get { return _pictures; }
        }

        /// <summary>
        ///     Gets the count of the number of pictures in the sequence.
        /// </summary>
        /// <value>The of the number of pictures in the sequence.</value>
        public int Count
        {
            get { return _pictures.Count; }
        }

        /// <summary>
        ///     Gets a unique name that can be used to identify this object.
        /// </summary>
        /// <value>The unique name of this item.</value>
        /// <remarks>The name must never be <see langword="null" />, empty or whitespace.</remarks>
        public override string Name
        {
            get { return _name; }
        }

        #region IEnumerable<Picture> Members

        /// <summary>
        ///     Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        ///     A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<Picture> GetEnumerator()
        {
            return _pictures.GetEnumerator();
        }

        /// <summary>
        ///     Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        ///     An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IPicture Members

        /// <summary>
        ///     Gets the current image that should be displayed by consumers of this image..
        /// </summary>
        /// <value>The active image.</value>
        [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
        public Image ActiveImage
        {
            get
            {
                if(_pictures.Count <= 0)
                    throw new UninitializedException(string.Format(CultureInfo.InvariantCulture, Resources.Error_PictureSequenceContainsNoPictures, _name));

                if(_current == null)
                    throw new UninitializedException(string.Format(CultureInfo.InvariantCulture, Resources.Error_PictureSequenceHasNoCurrentPicture, _name));

                return _current.Image;
            }
        }

        #endregion

        /// <summary>
        ///     Adds pictures in bulk.
        /// </summary>
        /// <param name="pictures">The pictures to add.</param>
        public void AddPictures([CanBeNull] params Picture[] pictures)
        {
            if(pictures == null)
                return;

            foreach(var picture in pictures)
                _pictures.Add(picture);
        }

        /// <summary>
        ///     Called whenever the Cue ticks.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void Tick([CanBeNull] object sender, [CanBeNull] EventArgs e)
        {
            try
            {
                Dispatcher.Invoke(Increment);
            }
            catch(Exception ex)
            {
                ErrorManager.OnUnhandledException(ex);
            }
        }

        /// <summary>
        ///     Initializes the picture sequence before a game starts.
        /// </summary>
        public void Initialize()
        {
            Dispatcher.Invoke(Increment);
        }

        /// <summary>
        ///     Increments the picture.
        /// </summary>
        /// <exception cref="UninitializedException">PictureSequence has no current picture.</exception>
        /// <exception cref="UnknownResourceException">
        ///     The picture returned does not belong to the 'Pictures' dictionary:  +
        ///     picture.Name
        /// </exception>
        private void Increment()
        {
            lock(_tickLock)
            {
                var picture = GetNext();
                if(picture == null)
                    throw new UninitializedException(string.Format(CultureInfo.InvariantCulture, Resources.Error_PictureSequenceHasNotCurrentPicture, _name));
                if(!_pictures.Contains(picture))
                    throw new UnknownResourceException(string.Format(CultureInfo.InvariantCulture, Resources.Error_PictureDoesNotBelongToSequence, picture.Name));
                _current = picture;
            }
            OnPropertyChanged("ActiveImage");
        }

        /// <summary>
        ///     Gets the next picture in the sequence.
        /// </summary>
        /// <returns>
        ///     The picture that should now be displayed.  Must belong to the <see cref="Pictures" /> dictionary and must
        ///     never be <see langword="null" />.
        /// </returns>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        [NotNull]
        protected abstract Picture GetNext();
    }
}